package jmine.tec.web.servlet.rest;

import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import jmine.tec.web.servlet.rest.exception.HttpErrorException;

import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import bancosys.tec.persist.Persistable;
import bancosys.tec.persist.dao.BaseDAO;
import bancosys.tec.persist.dao.BaseDAOFactory;
import bancosys.tec.persist.dao.BeanNotFoundException;

/**
 * {@link RestImplementor} para facilitar criar servicos Rest para entidades
 * 
 * @author takeshi
 * @param <E> o tipo do {@link Persistable}
 */
public abstract class AbstractEntityRestService<E extends Persistable> extends RestImplementor {

    private final Class<E> concreteClass;

    private BaseDAOFactory baseDAOFactory;

    /**
     * C'tor
     * 
     * @param concreteClass {@link Class}
     */
    public AbstractEntityRestService(Class<E> concreteClass) {
        super();
        this.concreteClass = concreteClass;
    }

    /**
     * {@inheritDoc}
     */
    @Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED, rollbackFor = { IOException.class,
            BeanNotFoundException.class, HttpErrorException.class }, readOnly = true)
    @Override
    public Object get(HttpServletRequest request, HttpServletResponse response) throws IOException, BeanNotFoundException,
            HttpErrorException {
        return super.get(request, response);
    }

    /**
     * {@inheritDoc}
     */
    @Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED, rollbackFor = { IOException.class,
            BeanNotFoundException.class, HttpErrorException.class })
    @Override
    public Object post(HttpServletRequest request, HttpServletResponse response) throws IOException, BeanNotFoundException,
            HttpErrorException {
        return super.post(request, response);
    }

    /**
     * {@inheritDoc}
     */
    @Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED, rollbackFor = { IOException.class,
            BeanNotFoundException.class, HttpErrorException.class })
    @Override
    public Object put(HttpServletRequest request, HttpServletResponse response) throws IOException, BeanNotFoundException,
            HttpErrorException {
        return super.put(request, response);
    }

    /**
     * {@inheritDoc}
     */
    @Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED, rollbackFor = { IOException.class,
            BeanNotFoundException.class, HttpErrorException.class })
    @Override
    public Object delete(HttpServletRequest request, HttpServletResponse response) throws IOException, HttpErrorException,
            BeanNotFoundException {
        return super.delete(request, response);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Object get(String identification, Map<String, String[]> parameters) throws BeanNotFoundException, HttpErrorException {
        return this.getDAO().findByPk(Long.valueOf(identification));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Object get(Map<String, String[]> parameters) throws BeanNotFoundException, HttpErrorException {
        if (parameters.isEmpty()) {
            return this.getDAO().findAll();
        }
        return this.makeQuery(this.getDAO(), parameters);
    }

    /**
     * Deve
     * 
     * @param dao {@link BaseDAO}
     * @param parameters {@link Map}
     * @return {@link Map} de {@link String} para {@link String}
     * @throws HttpErrorException e
     */
    protected Collection<? extends E> makeQuery(BaseDAO<E> dao, Map<String, String[]> parameters) throws HttpErrorException {
        return this.listByNaturalKey(dao, parameters);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected final Object prepareToSerialize(Object e, Map<String, String[]> map) throws HttpErrorException {
        return this.prepareEntityForSerialization(this.concreteClass.cast(e), map);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Object shrinkAll(Collection<?> collection, Map<String, String[]> parameters) throws HttpErrorException {
        Map<String, String> ret = new HashMap<String, String>(collection.size());
        for (Object object : collection) {
            E e = this.concreteClass.cast(object);
            ret.put(String.valueOf(e.getPk()), this.getNaturalKeyProperty(e));
        }
        return ret;
    }

    /**
     * @param e E
     * @param map map of parameters
     * @return {@link Object}
     * @throws HttpErrorException e
     */
    protected Object prepareEntityForSerialization(E e, Map<String, String[]> map) throws HttpErrorException {
        return e;
    }

    /**
     * Subclasses devem devolver o nome do parametro que deve vir da query string do request que contem o atributo mnemonico.
     * 
     * @return String
     */
    protected abstract String getNaturalKeyParameterName();

    /**
     * Devolve o natural key do mapa
     * 
     * @param parameters {@link Map}
     * @return {@link String}
     * @throws HttpErrorException e
     */
    protected String getNaturalKey(Map<String, String[]> parameters) throws HttpErrorException {
        String[] strings = parameters.get(this.getNaturalKeyParameterName());
        if (strings != null && strings.length > 0) {
            return strings[0];
        }
        return null;
    }

    /**
     * Devolve o valor da propriedade 'natural key' que esta entidade usa
     * 
     * @param instance E
     * @return String
     */
    protected abstract String getNaturalKeyProperty(E instance);

    /**
     * Subclasses podem sobrescrever este metodo
     * 
     * @param dao {@link BaseDAO}
     * @param parameters {@link Map}
     * @return {@link Object}
     * @throws HttpErrorException e
     */
    protected Collection<? extends E> listByNaturalKey(BaseDAO<E> dao, Map<String, String[]> parameters) throws HttpErrorException {
        return dao.findByLikeNaturalKey(this.getNaturalKey(parameters));
    }

    /**
     * Subclasses podem sobrescrever este metodo
     * 
     * @param dao {@link BaseDAO}
     * @param parameters {@link Map}
     * @return E
     * @throws BeanNotFoundException e
     * @throws HttpErrorException e
     */
    protected E findByNaturalKey(BaseDAO<E> dao, Map<String, String[]> parameters) throws BeanNotFoundException, HttpErrorException {
        return this.getDAO().findByNaturalKey(this.getNaturalKey(parameters));
    }

    /**
     * Deve ser sobrescrito por subclasses para modificarem a entidade de acordo os parametros do mapa
     * 
     * @param entity e
     * @param parameters {@link Map}
     * @throws HttpErrorException e
     */
    protected void modifyEntity(E entity, Map<String, String[]> parameters) throws HttpErrorException {
        throw new HttpErrorException(HttpServletResponse.SC_FORBIDDEN, this.getNaturalKeyProperty(entity));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Object post(Map<String, String[]> parameters) throws BeanNotFoundException, HttpErrorException {
        E entity = this.findByNaturalKey(this.getDAO(), parameters);
        this.modifyEntity(entity, parameters);
        entity.getPersister().save();
        return entity;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Object post(String identification, Map<String, String[]> parameters) throws BeanNotFoundException, HttpErrorException {
        E persistable = this.getDAO().findByPk(identification);
        this.modifyEntity(persistable, parameters);
        persistable.getPersister().save();
        return persistable;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Object delete(Map<String, String[]> parameters) throws BeanNotFoundException, HttpErrorException {
        E entity = this.findByNaturalKey(this.getDAO(), parameters);
        entity.getPersister().remove();
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Object delete(String identification, Map<String, String[]> parameters) throws BeanNotFoundException, HttpErrorException {
        E persistable = this.getDAO().findByPk(identification);
        persistable.getPersister().remove();
        return null;
    }

    /**
     * @return {@link BaseDAO}
     */
    public BaseDAO<E> getDAO() {
        return this.baseDAOFactory.getGenericDAO(this.concreteClass);
    }

    /**
     * @return the baseDAOFactory
     */
    public BaseDAOFactory getBaseDAOFactory() {
        return this.baseDAOFactory;
    }

    /**
     * @param baseDAOFactory the baseDAOFactory to set
     */
    public void setBaseDAOFactory(BaseDAOFactory baseDAOFactory) {
        this.baseDAOFactory = baseDAOFactory;
    }

}
